    ; 内核程序
    ; @author: Milon
    ; @date: 2024/04/29

%include "./common/global_defs.h"

section core_header
    length        dd  core_end                      ; 0x00 内核总长度
    init_entry    dd  init                          ; 0x04 内核入口点
    position      dq  0                             ; 0x08 内核加载的线性地址

section core_data
    welcome       db "Executing in 64-bit mode.Init MP", 249, 0
    cpu_init_ok   db " CPU(s) ready.", 0x0d, 0x0a, 0
    exceptm       db "A exception raised,halt.",0
    buffer        times 256 db 0

    acpi_error    db "ACPI is not supported or data error.", 0x0d, 0x0a, 0
    num_cpus      db 0                               ;逻辑处理器数量
    cpu_list      times 256 db 0                     ;Local APIC ID的列表
    lapic_addr    dd 0                               ;Local APIC的物理地址

    ioapic_addr   dd 0                               ;I/O APIC的物理地址
    ioapic_id     db 0                               ;I/O APIC ID

    ack_cpus      db 0                               ;处理器初始化应答计数

    clocks_1ms    dd 0                               ;处理器在1ms内经历的时钟数

    tss_ptr       dq 0                               ;任务状态段TSS从此处开始
    
    ; 系统功能调用表
    sys_entry     dq get_screen_row                 ; #0  返回下一个可用的屏幕行坐标
                  dq get_cmos_time                  ; #1  返回当前时间
                  dq put_cstringxy64                ; #2  在指定的坐标位置打印字符串
                  dq create_process                 ; #3  创建进程
                  dq get_current_pid                ; #4  返回当前进程PID
                  dq terminate_process              ; #5  终止当前进程
                  dq get_cpu_number                 ; #6  获取当前处理器的编号
                  dq create_thread                  ; #7  创建一个线程
                  dq get_current_tid                ; #8  返回当前线程的标识
                  dq thread_exit                    ; #9  终止当前线程
                  dq memory_allocate                ; #10 用户空间的内存分配
                  dq thread_join                    ; #11 等待指定的线程
                  dq init_mutex                     ; #12 初始化互斥锁
                  dq acquire_mutex                  ; #13 获取互斥锁
                  dq release_mutex                  ; #14 释放互斥锁
                  dq thread_sleep                   ; #15 线程休眠
                  dq debug                          ; #16 调试代码用


    pcb_ptr       dq 0                              ; 进程控制块PCB首节点的线性地址


section core_code

%include "./common/core_utils64_mp.asm"
%include "./common/user_static64.asm"

    bits 64


debug:                                              ; 调试用，反转屏幕左上角第二个字符
    mov r10, UPPER_TEXT_VIDEO
    not byte [r10 + 1]
    jmp $


general_interrupt_handler:                          ; 通用中断处理过程
    iretq



general_exception_handler:                          ; 通用异常处理过程
                                                    ; 在24行0列显示红底白字的错误信息
    mov r15, [rel position]
    lea rbx, [r15 + exceptm]
    mov dh, 24
    mov dl, 0
    mov r9b, 0x4f
    call put_cstringxy64
    
    cli
    hlt


general_8259ints_handler:                           ; 通用8259中断处理过程
    push rax

    mov al, 0x20                                    ; 中断结束命令EOI
    out 0x20, al                                    ; 向主片发送中断结束命令
    out 0xa0, al                                    ; 向从片发送中断结束命令

    pop rax
    iretq


syscall_procedure:                                  ; 系统调用的处理过程
                                                    ; RCX保存RIP，R11保存RFLAGS（由处理器完成）
                                                    ; RBP和R15被此例程占用
                                                    ; 输入：rax = 系统调用功能号

    ; 快速系统调用不会切换栈，需要手动切换
    ; IA32_FMASK已将中断标志位IF清零，此时可以放心切换栈
    swapgs                                          ; 使GS指向CPU专属数据区
    mov [gs:0], rsp                                 ; 保存当前任务的3特权级栈指针
    mov rsp, [gs:128 + 4]                           ; 从CPU专属数据区拿出0特权级栈指针
    push qword [gs:0]                               ; 把刚刚保存的3特权级栈指针压入当前栈
    swapgs
    sti                                             ; 栈切换完成后即可恢复中断

    push r15
    mov r15, [rel position]                         ; 获取内核加载起始线性地址
    add r15, [r15 + rax * 8 + sys_entry]            ; 取出sys_entry中对应功能号的偏移地址，再加上内核加载起始线性地址，得到系统调用线性地址
    call r15
    pop r15

    cli
    pop rsp                                         ; 恢复3特权级栈
    o64 sysret                                      ; 从快速系统调用返回64位模式



handle_waiting_thread:                              ; 处理等待其它线程的线程
                                                    ; 输入：R11=线程控制块TCB的线性地址
        push rbx
        push rdx
        push r11

        mov rbx, r11

        mov rdx, [r11 + 56]                         ; 获取等待线程的标识
        call search_for_thread_id                   ; 搜索指定线程
        or r11, r11                                 ; 判断返回的TCB线性地址是否为0
        jz .notify                                  ; 如果为0说明等待的线程已被清理，则唤醒被处理的线程

        cmp qword [r11 + 16], TASK_STATUS_TERM      ; 如果等待的线程已终止，则唤醒被处理的线程，否则返回
        jne .return
        
    .notify:
        mov qword [rbx + 16], TASK_STATUS_READY     ; 线程状态设为就绪
        mov r11, LAPIC_START_ADDR                   ; Local APIC的线性地址
        mov dword [r11 + 0x310], 0
        mov dword [r11 + 0x300], 0x000840fe         ; 向所有处理器发送线程认领中断

    .return:
        pop r11
        pop rdx
        pop rbx
        ret


handle_waiting_flag:                                ; 处理等待标志的线程
                                                    ; 输入：R11=线程控制块TCB的线性地址
        push rax
        push rbx
        push rcx

        mov rbx, [r11 + 56]                         ; 获取线程等待信号量线性地址
        xor rax, rax
        mov rcx, 1
        lock cmpxchg [rbx], rcx                     ; 尝试获取锁
        jnz .return                                 ; 如果没有获取成功，则继续休眠
        
        mov qword [r11 + 16], TASK_STATUS_READY     ; 如果成功获取锁，则设为就绪态等待下次CPU调度
        mov rbx, LAPIC_START_ADDR                   ;Local APIC的线性地址
        mov dword [rbx + 0x310], 0
        mov dword [rbx + 0x300], 0x000840fe         ;向所有处理器发送线程认领中断

    .return:
        pop rcx
        pop rbx
        pop rax
        ret


handle_waiting_sleep:                               ; 处理睡眠的线程
                                                    ; 输入：R11=线程控制块TCB的线性地址
        push rax

        dec qword [r11 + 56]                        ; 休眠时间单位长度 - 1
        cmp qword [r11 + 56], 0
        jne .return                                 ; 如果剩余休眠时间不等于0，则继续休眠

        mov qword [r11 + 16], TASK_STATUS_READY     ; 线程状态设为就绪

        mov rax, LAPIC_START_ADDR
        mov dword [rax + 0x310], 0x00000000
        mov dword [rax + 0x300], 0x000840fe         ; 向所有处理器广播新线程认领中断

    .return:
        pop rax
        ret


system_management_handler:                          ;系统管理中断的处理过程
        push rbx
        push r11

        mov rbx, [rel pcb_ptr]                      ; PCB链表首节点
        or rbx, rbx
        jz .return                                  ; 如果为0直接返回

    .nextp:
        mov r11, [rbx + 272]                        ; 取PCB的第一个TCB
        or r11, r11
        jz .get_nextp                               ; 如果当前PCB没有线程，则跳到下一个PCB

    .nextt:
        cmp qword [r11 + 16], TASK_STATUS_WAIT_SPEC_THREAD
        jne .next0
        call handle_waiting_thread
        jmp .gnext
    .next0:
        cmp qword [r11 + 16], TASK_STATUS_SLEEP_SEPC_TIME
        jne .next1
        call handle_waiting_sleep
        jmp .gnext
    .next1:
        cmp qword [r11 + 16], TASK_STATUS_WAIT_SIGNAL
        jne .gnext
        call handle_waiting_flag
    
    .gnext:
        mov r11, [r11 + 280]                        ; 取下一个TCB节点
        or r11, r11
        jnz .nextt
    .get_nextp:
        mov rbx, [rbx + 280]                        ; 取下一个PCB节点
        cmp rbx, [rel pcb_ptr]
        je .return                                  ; 如果转了一圈又回到头节点，则返回
        jmp .nextp

    .return:
        mov r11, LAPIC_START_ADDR
        mov dword [r11 + 0xb0], 0                   ; 发送中断结束命令EOI

        pop r11
        pop rbx
        iretq


init_mutex:                                         ; 初始化互斥锁
                                                    ; 输入：无
                                                    ; 输出：RDX=互斥锁变量线性地址
    push rcx
    push r13
    push r14

    mov rcx, 8
    call core_memory_allocate                       ; 在内核空间申请8字节
    mov rdx, r13

    pop r14
    pop r13
    pop rcx
    ret


acquire_mutex:                                      ; 获取互斥锁
                                                    ; 输入：RDX=互斥锁变量线性地址
        push rax
        push rbx
        push r11
        push r12
        push r13

        mov r11, 1
        xor rax, rax
        lock cmpxchg qword [rdx], r11
        jz .return                                  ; 加锁成功即可返回

        ; 加锁失败，休眠当前线程
        cli

        mov rax, LAPIC_START_ADDR                   ; Local APIC的线性地址
        mov dword [rax + 0x320], 0x00010000         ; 屏蔽定时器中断

        swapgs
        mov rax, qword [gs:8]                       ; 当前任务的PCB线性地址
        mov rbx, qword [gs:32]                      ; 当前线程的TCB线性地址
        swapgs

        ; 保存当前任务和线程的状态以便将来恢复执行。恢复时已获得互斥锁
        mov r13, cr3                                ; 保存原任务的分页系统
        mov qword [rax + 56], r13
        ;RAX和RBX不需要保存，将来恢复执行时从栈中弹出
        mov [rbx + 80], rcx
        mov [rbx + 88], rdx
        mov [rbx + 96], rsi
        mov [rbx + 104], rdi
        mov [rbx + 112], rbp
        mov [rbx + 120], rsp
        mov [rbx + 128], r8
        mov [rbx + 136], r9
        mov [rbx + 144], r10
        ;r11、R12和R13不需要设置，将来恢复执行时从栈中弹出
        mov [rbx + 176], r14
        mov [rbx + 184], r15
        mov r13, [rel position]
        lea r13, [r13 + .return]                    ; 线程恢复时，从.return处开始执行
        mov [rbx + 192], r13                        ; RIP域为中断返回点
        mov [rbx + 200], cs
        mov [rbx + 208], ss
        pushfq
        pop qword [rbx + 232]

        mov qword [rbx + 56], rdx                       ; 信号量线性地址 -> TCB
        mov qword [rbx + 16], TASK_STATUS_WAIT_SIGNAL   ; 线程状态设为 休眠并等待某个信号清零

        call search_for_a_ready_thread
        or r11, r11
        jz .sleep                                   ; 未找到就绪的线程，则让处理器回到空闲状态

        jmp resume_execute_a_thread                 ; 如果找到了就绪线程，则恢复并执行

    .sleep:
        swapgs
        mov qword [gs:0], 0                         ; 当前处理器无有效3特权级栈指针
        mov qword [gs:8], 0                         ; 当前处理器未在执行任务
        mov qword [gs:32], 0                        ; 当前处理器未在执行线程
        mov rsp, [gs:24]                            ; 切换到处理器的固有栈
        swapgs

        iretq

    .return:
        pop r13
        pop r12
        pop r11
        pop rbx
        pop rax
        ret


release_mutex:                                      ; 释放互斥锁
                                                    ; 输入：RDX=互斥锁变量线性地址
    push rax

    xor rax, rax
    xchg [rdx], rax

    pop rax
    ret


thread_sleep:                                       ;线程休眠
                                                    ;输入：RDX=以55ms为单位的时间长度
        push rax
        push rbx
        push r11
        push r12
        push r13

        ; 如果休眠0，直接返回
        cmp rdx, 0
        je .return

        ; 如果休眠，则阻塞当前线程
        
        cli                                         ; 关闭中断
        mov rsi, LAPIC_START_ADDR
        mov dword [rsi + 0x320], 0x00010000         ; 屏蔽Local APIC定时器中断

        swapgs
        mov rax, [gs:8]                             ; 当前任务PCB地址 -> rax
        mov rbx, [gs:32]                            ; 当前线程TCB地址 -> rbx
        swapgs

        mov r13, cr3
        mov [rax + 56], r13
        ; rax, rbx无需保存，将来过程返回时从栈中弹出
        mov [rbx + 80], rcx
        mov [rbx + 88], rdx
        mov [rbx + 96], rsi
        mov [rbx + 104], rdi
        mov [rbx + 112], rbp
        mov [rbx + 120], rsp
        mov [rbx + 128], r8
        mov [rbx + 136], r9
        mov [rbx + 144], r10
        ; r11, r12无需保存，将来过程返回时从栈中弹出
        mov [rbx + 176], r14
        mov [rbx + 184], r15
        mov r13, [rel position]
        lea r13, [r13 + .return]
        mov [rbx + 192], r13
        mov [rbx + 200], cs
        mov [rbx + 208], ss
        pushfq
        pop qword [rbx + 232]

        mov [rbx + 56], rdx                                 ; 休眠单位时间长度 -> TCB
        mov qword [rbx + 16], TASK_STATUS_SLEEP_SEPC_TIME   ; 设置线程状态：休眠指定时间

        call search_for_a_ready_thread
        or r11, r11
        jz .sleep

        jmp resume_execute_a_thread

    .sleep:
        swapgs
        mov qword [gs:0], 0                         ; 清空当前线程3特权级栈
        mov qword [gs:8], 0                         ; 清空当前任务PCB
        mov qword [gs:32], 0                        ; 清空当前线程TCB
        mov rsp, [gs:24]                            ; 恢复处理器固有栈
        swapgs

        iretq

    .return:
        ; ***** 调试指令，用于反转屏幕左上角第2个字符，观察休眠效果
        mov rbx, UPPER_TEXT_VIDEO
        not byte [rbx + 0x03]

        pop r13
        pop r12
        pop r11
        pop rbx
        pop rax
        ret


search_for_thread_id:                               ;查找指定标识的线程
                                                    ;输入：RDX=线程标识
                                                    ;输出：R11=线程的TCB线性地址
        push rbx

        mov rbx, [rel pcb_ptr]                      ; 获取第一个PCB
        mov r11, [rbx + 272]                        ; 获取第一个TCB

    .nextt:
        or r11, r11                        
        jz .nextp                                   ; 如果当前TCB=0，则遍历下一个PCB
        cmp [r11 + 8], rdx                          ; 如果当前TCB记录的线程ID=目标线程ID，则返回
        je .return
        mov r11, [r11 + 280]                        ; 遍历下一个TCB
        jmp .nextt

    .nextp:
        mov rbx, [rbx + 280]
        cmp rbx, [rel pcb_ptr]                      ; 获取下一个PCB节点，如果转一圈又回到头节点，则返回
        je .return
        mov r11, [rbx + 272]                        ; 下一个PCB的第一个TCB
        jmp .nextt

    .return:
        pop rbx
        ret    


thread_join:                                        ; 等待指定的线程结束
                                                    ; 输入：RDX=线程标识
        push rax
        push rbx
        push r11
        push r12
        push r13
        
        call search_for_thread_id                   ; 查找指定线程，如果没找到，则返回
        or r11, r11
        jz .return
    
        cmp qword [r11 + 16], TASK_STATUS_TERM      ; 如果线程已终止，则返回
        je .return

        ;被等待的线程还在运行，只能休眠并等待通知
        cli

        mov rax, LAPIC_START_ADDR                   ; Local APIC的线性地址
        mov dword [rax + 0x320], 0x00010000         ; 屏蔽定时器中断

        swapgs
        mov rax, qword [gs:8]                       ; 当前任务的PCB线性地址
        mov rbx, qword [gs:32]                      ; 当前线程的TCB线性地址
        swapgs

        ;保存当前任务和线程的状态以便将来恢复执行。
        mov r13, cr3                                ; 保存原任务的分页系统
        mov qword [rax + 56], r13
        ;RAX和RBX不需要保存，将来恢复执行时从栈中弹出
        mov [rbx + 80], rcx
        mov [rbx + 88], rdx
        mov [rbx + 96], rsi
        mov [rbx + 104], rdi
        mov [rbx + 112], rbp
        mov [rbx + 120], rsp
        mov [rbx + 128], r8
        mov [rbx + 136], r9
        mov [rbx + 144], r10
        ;r11、R12和R13不需要设置，将来恢复执行时从栈中弹出
        mov [rbx + 176], r14
        mov [rbx + 184], r15
        mov r13, [rel position]
        lea r13, [r13 + .return]                    ; 将来恢复执行时，是从例程调用返回
        mov [rbx + 192], r13                        ; RIP域为中断返回点
        mov [rbx + 200], cs
        mov [rbx + 208], ss
        pushfq
        pop qword [rbx + 232]

        mov qword [rbx + 16], TASK_STATUS_WAIT_SPEC_THREAD    ;置线程状态为“休眠并等待指定线程结束”
        mov [rbx + 56], rdx                          ; 设置被等待的线程标识

        call search_for_a_ready_thread
        or r11, r11
        jz .sleep                                    ; 未找到就绪的任务

        jmp resume_execute_a_thread                  ; 恢复并执行新线程

    .sleep:
        swapgs
        mov qword [gs:0], 0                          ; 当前处理器无有效3特权级栈指针
        mov qword [gs:8], 0                          ; 当前处理器未在执行任务
        mov qword [gs:32], 0                         ; 当前处理器未在执行线程
        mov rsp, [gs:24]                             ; 切换到处理器的固有栈
        swapgs

        iretq

    .return:
        pop r13
        pop r12
        pop r11
        pop rbx
        pop rax
        ret



create_thread:                                      ; 创建一个线程
                                                    ; 输入：rsi=线程入口的线性地址
                                                    ;       rdi=传递给线程的参数
                                                    ; 输出：rdx=线程ID
        push rax
        push rbx
        push rcx
        push r11
        push r12
        push r13
        push r14

        ; 创建线程控制块TCB
        mov rcx, 512
        call core_memory_allocate
        mov rbx, r13                                 ; rbx专门保存TCB线性地址

        ; 生成线程ID
        call generate_thread_id
        mov [rbx + 8], rax
        mov rdx, rax                                 ; 线程ID作为返回值
        mov qword [rbx + 16], TASK_STATUS_READY      ; 线程状态：就绪

        ; 申请新线程0特权级栈空间
        mov rcx, 4096 * 16
        call core_memory_allocate
        mov [rbx + 32], r14                          ; 栈顶 -> TCB.RSP0

        pushfq
        cli
        swapgs
        mov r11, [gs:8]                              ; 当前任务PCB线性地址 -> r11
        mov r12, [gs:32]                             ; 当前线程TCB线性地址 -> r12
        swapgs
        popfq
        
        ; 申请新线程3特权级栈空间
        mov rcx, 4096 * 16
        call user_memory_allocate

        sub r14, 32                                  ; 先在用户态栈空间中开辟32字节
        mov [rbx + 120], r14                         ; -> TCB.RSP

        ; 伪造一个线程过程返回地址
        lea rcx, [r14 + 8]                          
        mov [r14], rcx

        ; 在伪造的返回地址中填写指令机器码，调用9号系统功能，终止线程
        ; mov rax, 9
        mov byte [rcx + 0], 0xb8
        mov byte [rcx + 1], 0x09
        mov byte [rcx + 2], 0x00
        mov byte [rcx + 3], 0x00
        mov byte [rcx + 4], 0x00
        ; xor rdx, rdx
        mov byte [rcx + 5], 0x48
        mov byte [rcx + 6], 0x31
        mov byte [rcx + 7], 0xd2
        ; syscall
        mov byte [rcx + 8], 0x0f
        mov byte [rcx + 9], 0x05

        mov [rbx + 192], rsi                          ; 线程入口线性地址 -> TCB.RIP
        mov qword [rbx + 200], USER_CODE64_SEL        ; 线程的代码段选择子 -> TCB.CS
        mov qword [rbx + 208], USER_STACK64_SEL       ; 线程的栈段选择子 -> TCB.SS
        pushfq
        pop qword [rbx + 232]                         ; 填充TCB.RFLAGS
        mov qword [rbx + 240], SUGG_PREEM_SLICE       ; 填充TCB.线程抢占的时间片（毫秒）
        mov qword [rbx + 280], 0                      ; 填充下一个TCB地址

        ; 将新创建的TCB追加到当前TCB链表尾部
    .again:
        xor rax, rax
        lock cmpxchg [r12 + 280], rbx                 ; 如果找到链表尾部，则将新TCB线性地址追加进去
        jz .linked
        mov r12, [r12 + 280]                          ; 否则继续遍历下一个节点
        jmp .again

    .linked:
        mov rcx, LAPIC_START_ADDR                     ; Local APIC的线性地址
        mov dword [rcx + 0x310], 0
        mov dword [rcx + 0x300], 0x000840fe           ; 向所有处理器发送线程认领中断

        pop r14
        pop r13
        pop r12
        pop r11
        pop rcx
        pop rbx
        pop rax
        ret



get_current_tid:                                    ; 返回当前线程ID
                                                    ; 输出：rax = 线程ID
    pushfq
    cli

    swapgs
    mov rax, [gs:32]                                ; 获取当前线程的TCB线性地址
    mov rax, [rax + 8]                              ; 获取当前线程ID
    swapgs

    popfq
    ret


thread_exit:                                        ; 终止线程
                                                    ; 输入：RDX=返回码

        mov rsi, LAPIC_START_ADDR                   ; Local APIC的线性地址
        mov dword [rsi + 0x320], 0x00010000         ; 屏蔽定时器中断
        cli
        
        swapgs
        mov rbx, [gs:32]                            ; 当前线程TCB线性地址 -> rbx
        mov rsp, [gs:24]                            ; 切换为处理器固有栈
        mov qword [gs:0], 0                         ; 当前处理器无有效3特权级栈指针
        mov qword [gs:8], 0                         ; 当前处理器未在执行任务
        mov qword [gs:32], 0                        ; 当前处理器未在执行线程
        swapgs

        mov qword [rbx + 16], TASK_STATUS_TERM      ; 线程状态：终止
        mov [rbx + 24], rdx                         ; 填充线程结束code

        call search_for_a_ready_thread
        or r11, r11
        jz .sleep

        jmp resume_execute_a_thread

    .sleep:
        iretq


create_process:                                     ; 创建新的任务（包括主线程）
                                                    ; 输入：R8=程序的起始逻辑扇区号
        push rax
        push rbx
        push rcx
        push r11
        push r12
        push r13
        push r14
        push r15


        ; 申请PCB的内存，此处申请512字节，但PCB实际只占288字节
        mov rcx, 512
        call core_memory_allocate
        mov r11, r13                                 ; r11专门保存PCB线性地址

        call core_memory_allocate                    ; 申请TCB内存
        mov r12, r13                                 ; r12专门保存TCB线性地址

        mov qword [r11 + 272], r12                   ; 在PCB中登记第一个TCB
        mov qword [r11 + 24], USER_ALLOC_START       ; 写入下一次内存分配时可用的起始线性地址

        call copy_current_pml4                       ; 基于当前活动的四级头表创建新任务的四级头表
        mov [r11 + 56], rax                          ; 新创建的四级头表物理地址写回PCB

        ; 临时切换到新任务的四级头表
        mov r15, cr3
        mov cr3, rax

        ; 清空新任务四级头表的前半部分
        mov rax, 0xffff_ffff_ffff_f000
        mov rcx, 256
    .clsp:
        mov qword [rax], 0
        add rax, 8
        loop .clsp

        ; 刷新TLB
        mov rax, cr3
        mov cr3, rax

        ; 分配主线程RSP0的栈空间
        mov rcx, 4096 * 16
        call core_memory_allocate
        mov [r12 + 32], r14                          ; 填写TCB中的RSP0

        ; 分配主线程RSP的栈空间
        call user_memory_allocate
        mov [r12 + 120], r14

        mov qword [r11 + 16], TASK_STATUS_READY      ; 任务状态就绪

        ; 从硬盘读取用户程序
        mov rcx, 512
        call user_memory_allocate
        mov rbx, r13 
        mov rax, r8
        call read_hard_disk_0

        mov [r13 + 16], r13                          ; 用户程序加载线性地址会写到用户程序头部
        mov r14, r13
        add r14, [r13 + 8]                           ; 得到用户程序入口点线性地址
        mov [r12 + 192], r14                         ; 回写TCB的RIP

        ; 判断用户程序长度
        mov rcx, [r13]
        test rcx, 0x1ff
        jz .align_512
        add rcx, 512
        shr rcx, 9
        shl rcx, 9

    .align_512:
        sub rcx, 512
        jz .rdok
        call user_memory_allocate
        mov rbx, r13
        inc rax
        shr rcx, 9                                   ; 除以512，得到还需读取的扇区数
    .b1:
        call read_hard_disk_0
        inc rax
        loop .b1

    .rdok:
        mov qword [r12 + 200], USER_CODE64_SEL
        mov qword [r12 + 208], USER_STACK64_SEL
        pushfq
        pop qword [r12 + 232]

        mov qword [r12 + 240], SUGG_PREEM_SLICE      ; 推荐线程执行时间片

        ; 生成PID
        call generate_process_id
        mov [r11 + 8], rax

        ; 生成主线程ID
        call generate_thread_id
        mov [r12 + 8], rax

        mov qword [r12 + 280], 0                     ; 下一个TCB节点的线性地址，0代表没有

        call append_to_pcb_link

        mov cr3, r15                                 ; 切换回原任务的地址空间

        mov rsi, LAPIC_START_ADDR
        mov dword [rsi + 0x310], 0
        mov dword [rsi + 0x300], 0x000840fe          ; 向所有处理器发送任务/线程认领中断

        pop r15
        pop r14
        pop r13
        pop r12
        pop r11
        pop rcx
        pop rbx
        pop rax
        ret


    _append_lock  dq 0

append_to_pcb_link:                                 ; 在PCB链上追加任务控制块
                                                    ; 输入：R11=PCB线性基地址
        push rax
        push rbx
        push rdx

        pushfq
        cli
        mov rdx, [rel position]                 
        lea rdx, [rdx + _append_lock]
        call acquire_mutex                          ; 为了防止多个线程同时操作PCB链表，需要加锁

        mov rbx, [rel pcb_ptr]
        or rbx, rbx
        jnz .not_empty
        mov [r11], r11                              ; 如果当前PCB链表为空，则第一个节点头、尾都指向自己
        mov [r11 + 280], r11
        mov [rel pcb_ptr], r11
        jmp .return

    .not_empty:
        mov rax, [rbx]                              ; 头节点的前驱，也就是尾节点->rax
        mov [rax + 280], r11                        ; 尾节点后继节点指向新PCB
        mov [r11], rax                              ; 新节点前驱指向尾节点
        mov [r11 + 280], rbx                        ; 新PCB后继指向头节点
        mov [rbx], r11                              ; 头节点前驱指向新PCB

    .return:
        call release_mutex                          ; 释放互斥锁
        popfq

        pop rdx
        pop rbx
        pop rax
        ret



get_current_pid:                                    ; 获取当前任务（进程）的id
                                                    ; 输出：RAX=PID
    pushfq
    cli

    swapgs
    mov rax, [gs:8]                                 ; 获取当前任务的PCB线性地址
    mov rax, [rax + 8]                              ; 获取PID
    swapgs

    popfq
    ret


terminate_process:                                  ; 终止当前任务
        mov rsi, LAPIC_START_ADDR
        mov dword [rsi + 0x320], 0x00010000         ; 屏蔽定时器中断

        cli
        
        swapgs
        mov rax, [gs:8]                             ; 当前任务PCB线性地址 -> rax
        mov qword [rax + 16], TASK_STATUS_TERM      ; 任务状态：终止
        mov rax, [gs:32]                            ; 当前线程TCB线性地址 -> rax
        mov qword [rax + 16], TASK_STATUS_TERM      ; 线程状态：终止 
        mov qword [gs:0], 0                         ; 清空处理器专属数据区的3特权级栈
        mov rsp, [gs:24]                            ; 处理器固有栈 -> rsp

        mov qword [gs:8], 0                         ; 清空当前PCB
        mov qword [gs:32], 0                        ; 清空当前TCB
        swapgs
        
        call search_for_a_ready_thread
        or r11, r11
        jz .sleep

        jmp resume_execute_a_thread

    .sleep:
        iretq


    _ap_string      db 249, 0

ap_to_core_entry:                                   ; AP初始化代码 part2

        ; 使用高端线性地址加载GDTR、IDTR
        mov rax, UPPER_SDA_LINEAR
        lgdt [rax + 0x02]
        lidt [rax + 0x0c]

        ; 为当前处理器创建64位模式下的专属栈
        mov rcx, 4096
        call core_memory_allocate
        mov rsp, r14

        ; 创建当前处理器的专属存储区（含TSS），并安装TSS描述符到GDT
        mov rcx, 256
        call core_memory_allocate
        lea rax, [r13 + 128]
        call make_tss_descriptor

        mov r15, UPPER_SDA_LINEAR
        mov r8, [r15 + 0x04]                        ; GDT基地址 -> r8
        movzx rcx, word [r15 + 0x02]                ; GDT界限值 -> rcx
        ; 将TSS描述符追加到后面
        mov [r8 + rcx + 1], rsi
        mov [r8 + rcx + 9], rdi

        ; 重新加载GDTR
        add word [r15 + 0x02], 16
        lgdt [r15 + 0x02]

        ; 计算TSS描述符索引号
        shr cx, 3
        inc cx
        shl cx, 3

        ltr cx                                      ; 加载TR

        ; 将处理器专属数据区首地址保存到当前处理器的型号专属寄存器IA32_KERNEL_GS_BASE
        mov ecx, 0xc000_0102
        mov rax, r13
        mov rdx, r13
        shr rdx, 32
        wrmsr

        ;为快速系统调用SYSCALL和SYSRET准备参数
        mov ecx, 0x0c0000080                        ; 指定型号专属寄存器IA32_EFER
        rdmsr
        bts eax, 0                                  ; 设置SCE位，允许SYSCALL指令
        wrmsr

        mov ecx, 0xc0000081                         ; IA32_STAR
        mov edx, (RESVD_DESC_SEL << 16) | CORE_CODE64_SEL
        xor eax, eax
        wrmsr

        mov ecx, 0xc0000082                         ; IA32_LSTAR
        mov rax, [rel position]
        lea rax, [rax + syscall_procedure]          ; 只用EAX部分
        mov rdx, rax
        shr rdx, 32                                 ; 使用EDX部分
        wrmsr

        mov ecx, 0xc0000084                         ; IA32_FMASK
        xor edx, edx
        mov eax, 0x00047700                         ; TF=IF=DF=AC=0；IOPL=00
        wrmsr

        mov r15, [rel position]
        lea rbx, [r15 + _ap_string]
        call put_string64

        ; 填充CPU专属数据区
        swapgs
        mov qword [gs:8], 0
        movzx rax, byte [rel ack_cpus]
        mov qword [gs:16], rax
        mov qword [gs:24], rsp
        swapgs

        inc byte [rel ack_cpus]

        mov byte [AP_START_UP_ADDR + lock_var], 0   ; 释放自旋锁

        mov rsi, LAPIC_START_ADDR
        bts dword [rsi + 0xf0], 8                   ; 设置SVR寄存器，允许LAPIC

        sti

    ; AP初始化完成后，如果无事可做将停留在此，
    ; 通过响应中断跳转执行线程，中断返回后继续回到此处休息
    .do_idle:
        hlt
        jmp .do_idle



search_for_a_ready_thread:                          ; 查找一个就绪的线程并将其置为忙
                                                    ; 返回：R11=就绪任务的PCB线性地址
                                                    ;       R12=就绪线程的TCB线性地址
        push rax
        push rbx
        push rcx

        mov rcx, TASK_STATUS_BUSY

        swapgs
        mov rbx, [gs:8]                             ; 当前任务PCB线性地址 -> rbx
        mov r12, [gs:32]                            ; 当前线程TCB线性地址 -> r12
        swapgs
        mov r11, rbx
        cmp rbx, 0                                  ; 比较当前任务PCB线性地址是否为0
        jne .nextt
        mov rbx, [rel pcb_ptr]                      ; 如果为0，则从PCB链表首节点开始遍历
        mov r11, rbx
        mov r12, [r11 + 272]                        ; PCB首节点的TCB -> r12

    .nextt:
        cmp r12, 0
        je .nextp                                   ; 如果遍历到TCB链表末尾，则转向下一个PCB
        mov rax, TASK_STATUS_READY
        lock cmpxchg qword [r12 + 16], rcx          ; 如果TCB是就绪状态，则将其置为忙，并且将ZF置1
        jz .return                                  ; 找到就绪状态的TCB，返回
        mov r12, [r12 + 280]                        ; 否则遍历下一个TCB
        jmp .nextt

    .nextp:
        mov r11, [r11 + 280]                        ; 下一个PCB线性地址 -> r11
        cmp r11, rbx                                ; 比较下一个PCB线性地址是否与初始PCB相等
        je .fmiss                                   ; 如果相等则说明转了一圈又回来了，没有找到就绪线程
        mov r12, [r11 + 272]                        ; 下一个PCB的TCB线性地址 -> r12
        jmp .nextt

    .fmiss:
        xor r11, r11
        xor r12, r12

    .return:
        pop rcx
        pop rbx
        pop rax
        ret



resume_execute_a_thread:                            ; 恢复执行一个任务
                                                    ; 传入：R11=指定任务的PCB线性地址
                                                    ;       R12=线程的TCB线性地址
    ; 1ms内时钟周期数×时间片长度=定时器计数值
    mov eax, [rel clocks_1ms]
    mov ebx, [r12 + 240]
    mul ebx

    mov rsi, LAPIC_START_ADDR
    mov dword [rsi + 0x3e0], 0x0b                   ; 1分频
    mov dword [rsi + 0x320], 0xfd                   ; 单次击发模式，Fixed，中断号0xfd

    ; 切换到新任务的地址空间
    mov rbx, [r11 + 56]
    mov cr3, rbx

    swapgs
    mov [gs:8], r11                                 ; 设置线程所属任务为当前任务
    mov [gs:32], r12                                ; 设置新线程为当前线程
    mov rbx, [r12 + 32]                             ; 拿到TCB中的RSP0
    mov [gs:128 + 4], rbx                           ; 设置TSS的RSP0
    swapgs

    ; 恢复新任务各寄存器
    mov rcx, [r12 + 80]
    mov rdx, [r12 + 88]
    mov rdi, [r12 + 104]
    mov rbp, [r12 + 112]
    mov rsp, [r12 + 120]
    mov r8, [r12 + 128]
    mov r9, [r12 + 136]
    mov r10, [r12 + 144]

    mov r13, [r12 + 168]
    mov r14, [r12 + 176]
    mov r15, [r12 + 184]

    ; 构建中断返回的栈
    push qword [r12 + 208]                          ; SS
    push qword [r12 + 120]                          ; RSP
    push qword [r12 + 232]                          ; RFLAGS
    push qword [r12 + 200]                          ; CS
    push qword [r12 + 192]                          ; RIP

    mov dword [rsi + 0x380], eax                    ; 写入初始计数寄存器，开始计时

    mov rax, [r12 + 64]
    mov rbx, [r12 + 72]
    mov rsi, [r12 + 96]
    mov r11, [r12 + 152]
    mov r12, [r12 + 160]

    iretq                                           ; 转入新任务代码


new_task_notify_handler:                            ; 新任务/线程认领中断处理过程
        push rsi
        push r11
        push r12

        ; 给Local APIC发送EOI
        mov rsi, LAPIC_START_ADDR
        mov dword [rsi + 0xb0], 0

        swapgs
        cmp qword [gs:8], 0                         ; 比较处理器专属数据区中PCB线性地址
        swapgs
        jne .return                                 ; 如果不等于0，说明当前处理器正在执行其它任务，返回

        call search_for_a_ready_thread
        or r11, r11
        jz .return                                  ; 未能找到就绪任务，返回

        swapgs
        add rsp, 24                                 ; 恢复栈平衡
        mov qword [gs:24], rsp                      ; 保存处理器固有栈指针，以便将来返回
        swapgs

        jmp resume_execute_a_thread

    .return:
        pop r12
        pop r11
        pop rsi
        iretq
    

time_slice_out_handler:                             ; 时间片到期中断的处理过程
        push rax
        push rbx
        push r11
        push r12
        push r13

        ; 给Local APIC发送中断结束命令EOI
        mov r11, LAPIC_START_ADDR
        mov dword [r11 + 0xb0], 0

        call search_for_a_ready_thread
        or r11, r11
        jz .return

        ; 拿到当前任务PCB地址
        swapgs
        mov rax, [gs:8]                           ; 当前任务PCB的线性地址 -> rax
        mov rbx, [gs:32]                          ; 当前线程的TCB的线性地址 -> rbx
        swapgs

        ; 保存当前任务状态
        mov r13, cr3
        mov [rax + 56], r13                       ; 保存当前任务的分页
        ;mov [rax + 64], rax                      ; 不需设置，将来恢复执行时从栈中弹出
        ;mov [rax + 72], rbx                      ; 不需设置，将来恢复执行时从栈中弹出
        mov [rbx + 80], rcx
        mov [rbx + 88], rdx
        mov [rbx + 96], rsi
        mov [rbx + 104], rdi
        mov [rbx + 112], rbp
        mov [rbx + 120], rsp
        mov [rbx + 128], r8
        mov [rbx + 136], r9
        mov [rbx + 144], r10
        ;mov [rax + 152], r11                     ; 不需设置，将来恢复执行时从栈中弹出
        ; mov [rax + 160], r12
        ; mov [rax + 168], r13
        mov [rbx + 176], r14
        mov [rbx + 184], r15

        mov r13, [rel position]
        lea r13, [r13 + .return]
        mov [rbx + 192], r13                      ; RIP域为中断返回点
        mov [rbx + 200], cs
        mov [rbx + 208], ss
        pushfq
        pop qword [rbx + 232]

        mov qword [rbx + 16], TASK_STATUS_READY   ; 设置任务状态为就绪

        jmp resume_execute_a_thread

    .return:
        pop r13
        pop r12
        pop r11
        pop rbx
        pop rax
        iretq



init:                                               ; 内核入口点

        ; 将GDT映射到线性地址高端
        mov rax, UPPER_GDT_LINEAR
        mov [SDA_PHY_ADDR + 0x04], rax
        lgdt [SDA_PHY_ADDR + 0x02]

        ; 将栈指针映射到高端
        mov rax, UPPER_LINEAR_START
        add rsp, rax

        ; 回填内核起始加载线性地址到虚拟内存高端
        mov rax, UPPER_LINEAR_START
        add [rel position], rax

        ; 将内核跳转到线性地址高端执行
        mov rax, [rel position]
        add rax, .to_upper
        jmp rax

    .to_upper:
        ; 给32个异常安装中断门
        mov r9, [rel position]
        lea rax, [r9 + general_exception_handler]
        call make_interrupt_gate
        xor r8, r8
    .ist_exp:
        call mount_idt_entry
        inc r8
        cmp r8, 32
        jl .ist_exp
    
        ; 给剩余的中断号安装中断门
        lea rax, [r9 + general_interrupt_handler]
        call make_interrupt_gate
    .ist_int:
        call mount_idt_entry
        inc r8
        cmp r8, 256
        jl .ist_int

        mov rax, UPPER_IDT_LINEAR
        mov rbx, UPPER_SDA_LINEAR
        mov [rbx + 0x0e], rax                       ; 向系统数据区写入IDT线性地址
        mov word [rbx + 0x0c], 256 * 16 - 1         ; 向系统数据区写入IDT界限值
        lidt [rbx + 0x0c]

        ; 屏蔽8259中断，多处理器环境下不再使用8259芯片
        mov al, 0xff
        out 0x21, al

        lea rbx, [r9 + welcome]
        call put_string64

        mov r15, UPPER_SDA_LINEAR       
        xor rbx, rbx
        mov bx, [r15 + 0x02]                        ; 全局描述符表GDT的界限 -> bx
        inc bx                                      ; +1 = GDT长度
        add rbx, [r15 + 0x04]                       ; 下一个描述符安装地址 -> rbx

        ; syscall所需栈段描述符 #4 0x20 DPL=00
        mov dword [rbx + 0x00], 0x00000000
        mov dword [rbx + 0x04], 0x00009200

        ; sysret兼容模式代码段描述符，置空 #5 0x2b DPL=11
        mov dword [rbx + 0x08], 0x00000000
        mov dword [rbx + 0x0c], 0x00000000

        ; sysret栈段描述符 #6 0x33 DPL=11
        mov dword [rbx + 0x10], 0x00000000
        mov dword [rbx + 0x14], 0x0000f200

        ; sysret代码段描述符 #7 3b DPL=11
        mov dword [rbx + 0x18], 0x00000000
        mov dword [rbx + 0x1c], 0x0020f800

        ; 为BSP设置专属数据区
        mov rcx, 256
        call core_memory_allocate
        mov qword [r13 + 8], 0                      ; 当前任务PCB地址置为0
        mov qword [r13 + 16], 0                     ; 当前处理器编号为0
        mov qword [r13 + 24], rsp                   ; 本处理器固有栈
        ; 安装TSS描述符
        lea rax, [r13 + 128]
        call make_tss_descriptor
        mov [rbx + 0x20], rsi
        mov [rbx + 0x28], rdi

        add word [r15 + 2], 48                      ; 4个存储器段描述符（4×8B） + 1个TSS描述符（16B）
        lgdt [r15 + 2]

        mov cx, 0x40                                ; TSS描述符选择子
        ltr cx

        ; 将处理器专属数据区保存到IA32_KERNEL_GS_BASE
        mov ecx, 0xc000_0102
        mov rax, r13
        mov rdx, r13
        shr rdx, 32
        wrmsr

        ; 设置syscall/sysret参数
        ; syscall允许位置1
        mov ecx, 0xc0000080
        rdmsr
        or eax, 1
        wrmsr

        ; 设置IA32_STAR
        mov ecx, 0xc0000081
        mov edx, (RESVD_DESC_SEL << 16) | CORE_CODE64_SEL
        xor eax, eax
        wrmsr

        ; 设置IA32_LSTAR
        mov ecx, 0xc0000082
        mov rax, [rel position]
        lea rdx, [rax + syscall_procedure]
        mov rax, rdx
        shr rdx, 32
        wrmsr

        ; 设置IA32_FMASK
        mov ecx, 0xc0000084
        xor edx, edx
        mov eax, 0x00047700                         ; TF=IF=DF=AC=0；IOPL=00
        wrmsr

        ; 寻找ACPI申领内存
        cmp word [SDA_PHY_ADDR + 0x16], 0
        jz .acpi_err                                ; 如果系统数据区中的地址结构数为0，则跳转输出错误
        mov rsi, SDA_PHY_ADDR + 0x18
    .looking:
        cmp dword [rsi + 16], 3                     ; 类型为3就是申领内存
        jz .looked
        add rsi, 32
        jmp .looking

    .acpi_err:
        mov r15, [rel position]
        lea rbx, [r15 + acpi_error]
        call put_string64
        cli
        hlt

    .looked:
        ; 将ACPI申领内存映射为与物理地址相同的线性地址
        mov rbx, [rsi]                          ; ACPI申领内存 -> rbx
        mov rcx, [rsi + 8]                      ; 内存块长度 -> rcx
        add rcx, rbx                            ; ACPI申领内存 + 内存块长度
        mov rdx, 0xffff_ffff_ffff_f000
    .mapping:
        mov r13, rbx                            ; 物理地址作为线性地址传入
        mov rax, rbx                            
        and rax, rdx                            ; 物理地址低12位清0（4KB页对齐）
        or rax, 0x07                            ; US=RW=P=1
        call mapping_laddr_to_page
        add rbx, 0x1000
        cmp rbx, rcx
        jle .mapping

        ; 从物理地址0x60000开始，搜索根系统描述指针结构（RSDP）
        mov rbx, 0x60000
        mov rcx, 'RSD PTR '                     ; 签名，注意尾部空格
    .searc:
        cmp qword [rbx], rcx
        jz .finda
        add rbx, 0x10
        cmp rbx, 0xffff0
        jl .searc
        jmp .acpi_err

    .finda:
        cmp byte [rbx + 15], 2
        jnz .acpi_1                             ; 不等于2就跳转到acpi 1.0版本
        mov rbx, [rbx + 24]                     ; 拿到XSDT地址

        ; 检查是否是有效的XSDT表
        cmp dword [rbx], 'XSDT'
        jnz .acpi_err
        ; 校验和
        xor rcx, rcx
        mov ecx, [rbx + 4]                      ; XSDT表长度 -> rcx
        xor rax, rax
        push rbx
    .chksum:
        mov dl, [rbx]                           ; 将XSDT表的每个字节零扩展传送到rdx
        add al, dl                              ; 每个字节累加和 -> rax
        inc rbx
        loop .chksum
        pop rbx
        or al, al                               ; 检查最终累加和是否等于0
        jnz .acpi_err

        xor rdi, rdi
        mov edi, [rbx + 4]                      ; XSDT表长度 -> rdi
        add rdi, rbx                            ; 得到XSDT表末端地址
        add rbx, 36                             ; rbx = XSDT表中其它表地址数组
    .madt0:
        mov r11, [rbx]
        cmp dword [r11], 'APIC'
        jz .findm
        add rbx, 8
        cmp rbx, rdi
        jl .madt0
        jmp .acpi_err

    ; ACPI 1.0版本
    .acpi_1:
        mov ebx, [rbx + 16]                     ; RSDT物理地址 -> ebx

        ; 检查是否有效的RSDT
        cmp dword [ebx], 'RSDT'
        jnz .acpi_err
        xor rcx, rcx
        mov ecx, [ebx + 4]                      ; RSDT表长度 -> rcx
        xor rax, rax
        push rbx
    .chksum1:
        mov dl, [ebx]
        add al, dl
        inc ebx
        loop .chksum1
        pop rbx
        or al, al
        jnz .acpi_err

        mov edi, [ebx + 4]                      ; RSDT表长度 -> edi
        add edi, ebx                            ; RSDT表末端地址
        add ebx, 36                             ; RSDT表头之后，其它表数组地址 -> ebx
        xor r11, r11
    .madt1:
        mov r11d, [ebx]
        cmp dword [r11], 'APIC'
        jz .findm
        add ebx, 4
        cmp ebx, edi
        jl .madt1
        jmp .acpi_err

    .findm:
        ; 此时R11是MADT的物理地址
        mov edx, [r11 + 36]                     ; Local APIC物理地址 -> edx
        mov [rel lapic_addr], edx

        mov r15, [rel position]
        lea r15, [r15 + cpu_list]

        xor rdi, rdi
        mov edi, [r11 + 4]
        add rdi, r11                            ; MADT末端地址 -> rdi
        add r11, 44                             ; r11指向一系列中断控制器结构

    .enumd:
        cmp byte [r11], 0                       ; 如果类型=0，说明是Local APIC
        jz .lapic
        cmp byte [r11], 1                       ; 如果类型=1，说明是IO APIC
        jz .ioapic
        jmp .mend

    .lapic:
        cmp dword [r11 + 4], 0                  ; 如果标志为0，说明当前处理器不可用
        jz .mend
        mov al, [r11 + 3]                       ; Local APIC ID -> al
        mov [r15], al
        inc r15
        inc byte [rel num_cpus]
        jmp .mend

    .ioapic:
        mov al, [r11 + 2]                       ; IO APIC ID -> al
        mov [rel ioapic_id], al
        mov eax, [r11 + 4]                      ; IO APIC物理地址 -> eax
        mov [rel ioapic_addr], eax

    .mend:
        xor rax, rax
        mov al, [r11 + 1]                       ; 中断控制器结构长度 -> al
        add r11, rax                            ; r11指向下一个中断结构
        cmp r11, rdi                            ; 比较是否达到末端地址
        jl .enumd

        ; 映射Local APIC线性地址
        mov r13, LAPIC_START_ADDR
        xor rax, rax
        mov eax, [rel lapic_addr]
        or eax, 0x1f                            ; PCD=PWT=US=RW=P=1
        call mapping_laddr_to_page

        ; 映射IO APIC线性地址
        mov r13, IOAPIC_START_ADDR
        xor rax, rax
        mov eax, [rel ioapic_addr]
        or eax, 0x1f                            ; PCD=PWT=US=RW=P=1
        call mapping_laddr_to_page

    
        ; 测量处理器在1ms内有多少个时钟周期
        mov rsi, LAPIC_START_ADDR
        mov dword [rsi + 0x320], 0x10000        ; 设置Timer，单次击发模式，屏蔽中断
        mov dword [rsi + 0x3e0], 1011B          ; 1分频

        ; 1. 将索引端口指向寄存器B，同时屏蔽NMI
        mov al, 0x0b
        or al, 1000_0000B
        out 0x70, al

        ; 2. 设置寄存器B
        mov al, 0101_0010B                      ; 周期性中断允许PIE=1；更新周期结束中断允许UIE=1；24小时制
        out 0x71, al

        ; 设置寄存器A
        mov al, 0x8a
        out 0x70, al
        mov al, 0010_1101B                      ; 32.768kHZ，125ms
        out 0x71, al

        ; 读寄存器C
        mov al, 0x8c
        out 0x70, al
    .w0:
        in al, 0x71
        bt rax, 6                               ; 测试位6，是否产生周期性中断
        jnc .w0
        mov dword [rsi + 0x380], 0xffff_ffff    ; 设置初始计数寄存器
    .w1:
        in al, 0x71
        bt rax, 6                               ; 测试位6，是否产生周期性中断
        jnc .w1
        mov edx, [rsi + 0x390]                  ; 当前计数寄存器 -> edx

        mov eax, 0xffff_ffff
        sub eax, edx                            ; 初始计数值 - 当前计数值
        xor edx, edx
        mov ebx, 125                            ; 除以125得到1ms内的时钟周期数
        div ebx
        mov [rel clocks_1ms], eax

        ; RTC寄存器B同时阻断NMI
        mov al, 0x8b
        out 0x70, al
        mov al, 0001_0010B                      ; 设置寄存器B，只允许更新周期结束中断
        out 0x71, al

        ; 安装新任务认领中断
        mov r9, [rel position]
        lea rax, [r9 + new_task_notify_handler]
        call make_interrupt_gate
        cli                                     ; 安装中断向量前，先关闭中断
        mov r8, 0xfe                            ; 选择一个较大的向量号以便获得更高优先级
        call mount_idt_entry
        sti

        ; 安装时间片到期中断处理过程
        mov r9, [rel position]
        lea rax, [r9 + time_slice_out_handler]
        call make_interrupt_gate
        cli                                     
        mov r8, 0xfd                            
        call mount_idt_entry
        sti

        ; 安装系统管理中断处理过程
        mov r9, [rel position]
        lea rax, [r9 + system_management_handler]
        call make_interrupt_gate
        cli                                     
        mov r8, 0xfc                            
        call mount_idt_entry
        sti


        ; 以下初始化AP    
        ; AP初始化代码要求位于低端1M字节内，但现在内核被加载到线性地址高端，
        ; 所以需要将AP初始化代码一部分复制到低端1M字节内
        mov rsi, [rel position]
        lea rsi, [rsi + section.ap_init_block.start]
        mov rdi, AP_START_UP_ADDR                   ; 会覆盖loader代码，无所谓，loader用完一次之后就不再使用了
        mov rcx, ap_init_tail - ap_init
        cld
        rep movsb

        inc byte [rel ack_cpus]                     ; BSP应答计数

        ; 给其它处理器发送INIT IPI和SIPI，命令其初始化自己
        mov rsi, LAPIC_START_ADDR
        mov dword [rsi + 0x310], 0
        mov dword [rsi + 0x300], 0x000c4500         ; 发送INIT IPI
        mov dword [rsi + 0x300], (AP_START_UP_ADDR >> 12) | 0x000c4600     ; 发送Start Up IPI
        mov dword [rsi + 0x300], (AP_START_UP_ADDR >> 12) | 0x000c4600     ; 发送Start Up IPI

        ; 等待所有处理器应答完成
        mov al, [rel num_cpus]
    .wcpus:
        cmp al, [rel ack_cpus]
        jne .wcpus

        ; 显示已应答的处理器的数量信息
        xor r8, r8
        mov r8b, [rel ack_cpus]
        mov r15, [rel position]
        lea rbx, [r15 + buffer]
        call bin64_to_dec
        call put_string64

        lea rbx, [r15 + cpu_init_ok]
        call put_string64

        ; 设置8254定时器中断通过IO APIC发送到各处理器
        mov rdi, IOAPIC_START_ADDR
        mov dword [rdi], 0x14                       ; IO重定向表2号引脚前一半的端口号
        mov dword [rdi + 0x10], 0x000000fc          ; 物理模式，固定模式，中断向量号0xfc
        mov dword [rdi], 0x15                       ; IO重定向表2号引脚后一半的端口号
        mov dword [rdi + 0x10], 0x00000000          ; Local APIC ID = 0

        ; 创建shell进程
        mov r8, 50
        call create_process

        jmp ap_to_core_entry.do_idle                ; BSP工作完成，如果无事可做就跳转至空闲区等待响应中断



; ***** 以下是AP初始化代码part1 *****

section ap_init_block vstart=0
        bits 16

ap_init:
        mov ax, AP_START_UP_ADDR >> 4
        mov ds, ax

        SET_SPIN_LOCK al, byte [lock_var]           ; 初始化AP需要加锁

        mov ax, SDA_PHY_ADDR >> 4
        mov ds, ax

        lgdt [2]

        in al, 0x92                                 ; 南桥芯片内的端口
        or al, 0000_0010B
        out 0x92, al                                ; 打开A20

        cli                                         ; 中断机制尚未工作

        mov eax, cr0
        or eax, 1
        mov cr0, eax                                ;设置PE位

        jmp 0x08:AP_START_UP_ADDR + .flush          ;清流水线并串行化处理器

        [bits 32]
    .flush:
        mov eax, 0x0010
        mov ss, eax
        mov esp, 0x7e00

        ; 四级头表 -> cr3
        mov eax, PML4_PHY_ADDR                      ; PCD=PWT=0
        mov cr3, eax

        ; 开启物理地址扩展PAE
        mov eax, cr4
        bts eax, 5
        mov cr4, eax

        ; 设置型号专属寄存器IA32_EFER.LME，允许IA_32e模式
        mov ecx, 0x0c0000080
        rdmsr
        bts eax, 8                                  ; 设置LME位
        wrmsr

        ; 开启分页
        mov eax, cr0
        bts eax, 31                                 ; 设置PG位
        mov cr0, eax

        ; 进入64位模式
        jmp CORE_CODE64_SEL:AP_START_UP_ADDR + .to64

    .to64:
        bits 64

        mov rbx, UPPER_CORE_LINEAR + ap_to_core_entry
        jmp rbx

        lock_var    db  0

ap_init_tail:


section core_tail
core_end:
